﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection.Emit;
using System.Reflection;

namespace DymaticMethod
{
    class LCG
    {
        delegate void CopyPublicPropertiesDelegate<T, TU>
             (T source, TU target);

        static Dictionary<string, object> _del = new Dictionary<string, object>();


        public class PropertyMap
        {
            public PropertyInfo SourceProperty { get; set; }
            public PropertyInfo TargetProperty { get; set; }
        }

        private static Dictionary<string, PropertyMap[]> _maps = new Dictionary<string, PropertyMap[]>();

        protected virtual IList<PropertyMap> GetMatchingProperties
    (Type sourceType, Type targetType)
        {
            var sourceProperties = sourceType.GetProperties();
            var targetProperties = targetType.GetProperties();

            var properties = (from s in sourceProperties
                              from t in targetProperties
                              where s.Name == t.Name &&
                                    s.CanRead &&
                                    t.CanWrite &&
                                    s.PropertyType == t.PropertyType
                              select new PropertyMap
                              {
                                  SourceProperty = s,
                                  TargetProperty = t
                              }).ToList();
            return properties;
        }

        protected virtual string GetMapKey(Type sourceType,
            Type targetType)
        {
            var keyName = "Copy_";
            keyName += sourceType.FullName.Replace(".", "_");
            keyName += "_";
            keyName += targetType.FullName.Replace(".", "_");

            return keyName;
        }

        public void MapTypes(Type source, Type target)
        {
            if (source == null || target == null)
                return;

            var key = GetMapKey(source, target);
            if (_maps.ContainsKey(key))
                return;

            var props = GetMatchingProperties(source, target);
            _maps.Add(key, props.ToArray());
        }

        public static void GenerateCopyDelegate<T, TU>()
        {
            var className = GetClassName(typeof(T), typeof(TU));
            var args = new[] { typeof(T), typeof(TU) };
            var mod = typeof(Program).Module;

            var dm = new DynamicMethod(className, null, args, mod);
            var il = dm.GetILGenerator();
            var maps = _maps[className];

            foreach (var map in maps)
            {
                il.Emit(OpCodes.Ldarg_1);
                il.Emit(OpCodes.Ldarg_0);
                il.EmitCall(OpCodes.Callvirt,
                            map.SourceProperty.GetGetMethod(), null);
                il.EmitCall(OpCodes.Callvirt,
                            map.TargetProperty.GetSetMethod(), null);
            }
            il.Emit(OpCodes.Ret);
            var del = (CopyPublicPropertiesDelegate<T, TU>)dm.CreateDelegate(typeof(CopyPublicPropertiesDelegate<T, TU>));
            _del.Add(className, del);
        }

        private static string GetClassName(Type type, Type type_2)
        {
            return type.Name + type_2.Name;
        }

        static void CopyUsingLcg<T, TU>(T source, TU target)
        {
            var sourceType = source.GetType();
            var targetType = target.GetType();
            var className = GetClassName(sourceType, targetType);

            var del = (CopyPublicPropertiesDelegate<T, TU>)
                      _del[className];
            del.Invoke(source, target);
        }
    }
}
